package com.mischa.atc.tests;

import junit.framework.Assert;

import org.junit.Test;

import com.mischa.atc.Aircraft;
import com.mischa.atc.Coordinate;

public class AircraftTest {	
	@Test
	public void testThatAircraftIsCreatedWithDefaultValues()
	{
		Aircraft newAircraft = new Aircraft();
		
		Assert.assertEquals(0.00f,  newAircraft.getPosition().getX());
		Assert.assertEquals(0.00f,  newAircraft.getPosition().getX());
		Assert.assertEquals(0,      newAircraft.getAltitude());
		Assert.assertEquals(0,      newAircraft.getSpeed());
		Assert.assertEquals(360.0f, newAircraft.getHeading().get());
	}
	
	@Test
	public void PositionCannotBeDirectlySet()
	{
		Aircraft aircraft = new Aircraft();

		Assert.assertEquals(0f, aircraft.getPosition().getX());
		Assert.assertEquals(0f, aircraft.getPosition().getY());
		
		Coordinate position = aircraft.getPosition();
		position.setX(10.0f);
		position.setY(-12.5f);
		
		Assert.assertEquals(0f, aircraft.getPosition().getX());
		Assert.assertEquals(0f, aircraft.getPosition().getY());		
	}
	
	@Test
	public void SetSpeedIsRetained()
	{
		int speed = 250;
		Aircraft newAircraft = new Aircraft();
		newAircraft.setSpeed(speed);
		
		Assert.assertEquals(speed, newAircraft.getSpeed());
	}
	
	@Test
	public void NegativeSpeedIsNotAllowed()
	{
		int speed = -250;
		Aircraft newAircraft = new Aircraft();
		try
		{
			newAircraft.setSpeed(speed);
			Assert.fail("Expected IllegalArgumentException, but nothing was thrown");
		} catch(IllegalArgumentException ex) {
			// Test passes
		} catch(Exception ex) {
			Assert.failNotEquals("Unexpected exception", IllegalArgumentException.class, ex.getClass());
		}
	}
	
	
	
	@Test
	public void HeadingBetween0And360IsRetained()
	{
		HeadingIsConverted(130f, 130);
		HeadingIsConverted(0.989f, 0.989f);
		HeadingIsConverted(359.8f, 359.8f);
	}

	@Test
	public void HeadingSmallerThan0OrGreaterThan360IsConvertedToStandardRange()
	{
		HeadingIsConverted(-10f, 350);
		HeadingIsConverted(380.1234f, 20.1234f);
		HeadingIsConverted(721.75f, 1.75f);
	}
	
	@Test
	public void PureNorthHeadingsAreAlwaysRepresentedAs360()
	{
		HeadingIsConverted(0, 360);
		HeadingIsConverted(360, 360);
		HeadingIsConverted(720, 360);
		HeadingIsConverted(-360, 360);
	}
	

	private void HeadingIsConverted(float from, float to)
	{
		Aircraft aircraft = new Aircraft();
		aircraft.setHeading(from);
		// Compare values up to 1/10^3 degree
		Assert.assertEquals(to, aircraft.getHeading().get(), 1E-3);
		Assert.assertEquals(to, aircraft.getClearedHeading().get(), 1E-3);
	}
	
	
	@Test
	public void AltitudeCanBeSetAndRetrievedInMultiplesOf100Feet()
	{
		Aircraft aircraft = new Aircraft();
		aircraft.setAltitude(2340);
		Assert.assertEquals(2300, aircraft.getAltitude());
		Assert.assertEquals(2300, aircraft.getClearedAltitude());
	}
	
	@Test
	public void NegativeAltitudeIsNotAllowed()
	{
		int altitude = -250;
		Aircraft newAircraft = new Aircraft();
		try
		{
			newAircraft.setAltitude(altitude);
			Assert.fail("Expected IllegalArgumentException, but nothing was thrown");
		} catch(IllegalArgumentException ex) {
			// Test passes
		} catch(Exception ex) {
			Assert.failNotEquals("Unexpected exception", IllegalArgumentException.class, ex.getClass());
		}		
	}
	
	private class AircraftDummy extends Aircraft
	{
		public int turningDirection;
		
		@Override
		public void turn(float targetHeading, int turningDirection) {
			this.turningDirection = turningDirection;
		}
	}
	
	private void AssertThatAircraftTurnsInGivenDirection(float startDirection, float finalDirection, int expectedTurningDirection)
	{
		AircraftDummy aircraft = new AircraftDummy();
		aircraft.setHeading(startDirection);
		aircraft.turn(finalDirection);
		Assert.assertEquals(expectedTurningDirection, aircraft.turningDirection);
	}
	
	
	@Test
	public void TurnWithOneArgumentAutomaticallyDeterminesTheBestDirection()
	{
		final int CLOCKWISE = 1;
		final int COUNTERCLOCKWISE = -1;
		// Do some tests with north: [0, 180] is clockwise, <180, 360> is CCW
		AssertThatAircraftTurnsInGivenDirection(10, 20, CLOCKWISE);
		AssertThatAircraftTurnsInGivenDirection(360, 170, CLOCKWISE);
		AssertThatAircraftTurnsInGivenDirection(360, 180, CLOCKWISE);
		AssertThatAircraftTurnsInGivenDirection(360, 181, COUNTERCLOCKWISE);
		
		// Repeat the test with another heading 
		AssertThatAircraftTurnsInGivenDirection(30, 20, COUNTERCLOCKWISE);
		AssertThatAircraftTurnsInGivenDirection(30, 209, CLOCKWISE);
		AssertThatAircraftTurnsInGivenDirection(30, 210, CLOCKWISE);
		AssertThatAircraftTurnsInGivenDirection(30, 211, COUNTERCLOCKWISE);		
	}
	
	@Test
	public void AircraftTurnsWith30DegreeBankAngle()
	{
		// 30 degree angle, means (1093 * Tan(30 degrees) / speed) ~ 2.5 degrees per second
		// (http://www.flightlearnings.com/rate-of-turn/437/) 
		final float expectedHeadingAfter1Second = 
			(float) (360 - 1091 * Math.tan(Math.toRadians(30)) / 250);
		
		Aircraft aircraft = new Aircraft();
		aircraft.setSpeed(250);
		aircraft.turn(270);		

		aircraft.stepTime(1);		
		Assert.assertEquals(expectedHeadingAfter1Second, aircraft.getHeading().get(), 1E-3);
		
		// If the aircraft turns about 2.5 degrees per second, 90 degrees takes ~36 seconds
		aircraft.stepTime(36);
		Assert.assertEquals(270, aircraft.getHeading().get(), 1E-3);
	}
	
}
